﻿using System;
using System.IO;                    // FileReader and such
using System.Collections;           // ArrayLists and such 
using System.Collections.Generic;
using System.Text;
using Tedds_Tool.Instructions;
using Tedds_Tool.Compilers.LEET;
using Compiled_Tests.Utils;
using Tedds_Tool.Automatables;
using CommonInterfaces.Models;
using Compiled_Tests;
using Tedds_Tool.RulesBase;
using LEET.StandardModuals.Instructions;
using LEET.CommonInterfaces.Models;
using LEET.CommonInterfaces.Compilers;
using RulesBase.Model;
using LEET.Utilities;
using LEET.Application.Compilers.LEET;

namespace Tedds_Tool
{
    public class Compiler
    {
        private AssertionSet Asserts;
        private IInstructionFactory Factory;
        private IScriptFinder finder;
        private ICompilerConfig config;

        // uses default settings
        public Compiler(AssertionSet asserts, IInstructionFactory factory, IScriptFinder find) : this(asserts, factory, find, new CompilerConfig()) { }
        
        public Compiler(AssertionSet asserts, IInstructionFactory factory, IScriptFinder find, ICompilerConfig config)
        {
            Factory = factory;
            Asserts = asserts;
            finder = find;
            this.config = config;
        }

        public String Compile(String scriptName)
        {
            return Compile(scriptName, config);
        }

        public String Compile(String scriptName, ICompilerConfig config)
        {
            //TODO finish compiler reorganize
            ICompilerOptimizer optimizer = new CompilerOptimizer(Asserts);
            StringBuilder buf = new StringBuilder();

            buf.Append(RenderHeader());
            buf.Append(RenderScriptClassHeader(scriptName, config.ScriptInterface.ToString()));

            if (config.IncludeTestMethod)
                buf.Append(RenderTestMethod(scriptName));
            if (config.IncludeMainMethod)
                buf.Append(RenderMainMethod(scriptName, config.TestController.ToString()));

            if (ScriptInterfaces.TestScript.Equals(config.ScriptInterface))
            {
                using (TextReader reader = finder.FindTestScript(scriptName))
                {
                    IList<IInstruction> instructions = new List<IInstruction>();

                    string line;
                    while ((line = reader.ReadLine()) != null)
                        if (!string.IsNullOrEmpty(line.Trim()))
                        {
                            instructions.Add(Factory.Interpret(line));
                        }

                    if (instructions.Count <= 0)
                        throw new InvalidScriptException("Could not compile empty script " + scriptName);

                    buf.Append(RenderNewRunMethod(instructions));
                    buf.Append(RenderCommentedScript(instructions));
                }
            }
            else if (ScriptInterfaces.ExecutableRule.Equals(config.ScriptInterface))
            {
                IRule rule = null;
                buf.Append(RenderNewRuleRunMethod(rule));
            }

            buf.Append(RenderFooter());

            return buf.ToString();

        }

        private String RenderNewRuleRunMethod(IRule rule)
        {
            StringBuilder buf = new StringBuilder();

            buf.Append("        public IEnumerable<String> Run(IBox box)\r\n");
            buf.Append("        {\r\n");
            buf.Append("            this.Box = box;\r\n");
            buf.Append("            bool flag = true;\r\n");

            // preconditions
            buf.Append("            try\r\n");
            buf.Append("            {\r\n");
            foreach (IInstruction ins in rule.PreconditionInstructions)
            {
                buf.Append("                ")
                    .Append(ins.RenderStaticCall())
                    .Append("; Thread.Sleep(delay);\r\n");
            }
            buf.Append("            }\r\n");
            buf.Append("            catch\r\n");
            buf.Append("            {\r\n");
            buf.Append("                flag = false;\r\n");
            buf.Append("            }\r\n");

            //////////////////////////////////
            buf.Append("            if(flag)\r\n");
            buf.Append("            {\r\n");

            // implications
            buf.Append("                try\r\n");
            buf.Append("                {\r\n");
            foreach (IInstruction ins in rule.ImplicationInstructions)
            {
                buf.Append("                    ")
                    .Append(ins.RenderStaticCall())
                    .Append("; Thread.Sleep(delay);\r\n");
            }
            buf.Append("                }\r\n");
            buf.Append("                catch\r\n");
            buf.Append("                {\r\n");
            // consequences
            buf.Append("                    IList<String> consequences = new List<String>();\r\n");
            foreach (IConsequence consequence in rule.Consequences)
                buf.Append("                    consequences.Add(\"").Append(Utilities.Escape(consequence.Message)).Append("\");\r\n");
            buf.Append("                    return consequences;\r\n");
            buf.Append("                }\r\n");

            buf.Append("            }\r\n");
            buf.Append("            return new List<String>();\r\n");
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        private String RenderNewRunMethod(IList<IInstruction> instructions)
        {
            StringBuilder buf = new StringBuilder();
            int counter = 0;

            buf.Append("        public IEnumerable<IAutomationActionResult> Run(IBox box)\r\n");
            buf.Append("        {\r\n");
            buf.Append("            this.Box = box;\r\n");
            foreach (IInstruction ins in instructions)
            {
                buf.Append("            yield return ")
                    .Append(ins.RenderStaticCall())
                    .Append("; Thread.Sleep(delay);\r\n");
                counter++;
            }
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        /// <summary>
        /// Takes a script in LEETSp34k and compiles it into C# test code 
        /// executable under MbUnit.  Compiled tests will be saved as .cs 
        /// files in "Compiled Tests" -> "Generated Tests" until further notice.
        /// Note that error handling is VERY MINIMAL, so syntax errors in the 
        /// input script are likely to be translated directly to the output file.
        /// Note also that old files will be overwritten without confirmation.
        /// </summary>
        /// <param name="script">The TTL script, in .txt format, to compile.</param>
        /// 
        public String CompileScript(String scriptName, TextReader reader)
        {
            ICompilerOptimizer optimizer = new CompilerOptimizer(Asserts);
            StringBuilder buf = new StringBuilder();
            IList<IInstruction> instructions = new List<IInstruction>();
            
            string line;
            while ((line = reader.ReadLine()) != null)
                if (!string.IsNullOrEmpty(line.Trim()))
                {
                    instructions.Add(Factory.Interpret(line));
                }

            if (instructions.Count <= 0)
                throw new InvalidScriptException("Could not compile empty script " + scriptName);

            buf.Append(RenderHeader());
            buf.Append(RenderScriptClassHeader(scriptName, "ITestScript"));
            buf.Append(RenderMethods(instructions, optimizer));
            buf.Append(RenderMainAndTestMethod(scriptName, "TestController"));
            buf.Append(RenderRunMethod(instructions, optimizer));
            buf.Append(RenderFooter());

            buf.Append(RenderCommentedScript(instructions));

            return buf.ToString();
        }

        public String CompileDemo(string scriptName, TextReader reader)
        {
            ICompilerOptimizer optimizer = new CompilerOptimizer(Asserts);
            StringBuilder buf = new StringBuilder();
            IList<IInstruction> instructions = new List<IInstruction>();

            string line;
            while ((line = reader.ReadLine()) != null)
                if (!string.IsNullOrEmpty(line.Trim()))
                {
                    instructions.Add(Factory.Interpret(line));
                }

            if (instructions.Count <= 0)
                throw new InvalidScriptException("Could not compile empty script " + scriptName);

            buf.Append(RenderHeader());
            buf.Append(RenderScriptClassHeader(scriptName, "ITestScript"));
            buf.Append(RenderMethods(instructions, optimizer));
            buf.Append(RenderMainAndTestMethod(scriptName, "DemoController"));
            buf.Append(RenderRunMethod(instructions, optimizer));
            buf.Append(RenderFooter());

            return buf.ToString();
        }

        public StringBuilder CompileRule(IRule rule)
        {
            StringBuilder buf = new StringBuilder();
            ICompilerOptimizer optimizer = new CompilerOptimizer(Asserts);
            
            buf.Append(RenderHeader());
            buf.Append(RenderRuleClassHeader(rule.Name, "IExecutableRule"));
            
            buf.Append(RenderMethods(rule.PreconditionInstructions, optimizer));
            buf.Append(RenderMethods(rule.ImplicationInstructions, optimizer));

            buf.Append(RenderRuleRunMethod(rule, optimizer));
            buf.Append(RenderFooter());
            return buf;
        }

        internal virtual StringBuilder RenderCommentedScript(IList<IInstruction> instructions)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append("\r\n");
            buf.Append("/**\r\n");
            buf.Append("Original Script:\r\n");
            buf.Append("\r\n");
            foreach (IInstruction ins in instructions)
                buf.Append(ins.ToString()).Append("\r\n");
            buf.Append("*/\r\n");
            return buf;
        }

        internal virtual StringBuilder RenderRuleRunMethod(IRule rule, ICompilerOptimizer optimizer)
        {
            StringBuilder buf = new StringBuilder();
            
            buf.Append("        public IEnumerable<String> Run(IBox box)\r\n");
            buf.Append("        {\r\n");
            buf.Append("            this.Box = box;\r\n");
            buf.Append("            bool flag = true;\r\n");
            
            // preconditions
            buf.Append("            try\r\n");
            buf.Append("            {\r\n");
            foreach (IInstruction ins in rule.PreconditionInstructions)
            {
                buf.Append("                ")
                    .Append(optimizer.MethodCallFor(ins))
                    .Append("; Thread.Sleep(delay);\r\n");
            }
            buf.Append("            }\r\n");
            buf.Append("            catch\r\n");
            buf.Append("            {\r\n");
            buf.Append("                flag = false;\r\n");
            buf.Append("            }\r\n");

            //////////////////////////////////
            buf.Append("            if(flag)\r\n");
            buf.Append("            {\r\n");
            
            // implications
            buf.Append("                try\r\n");
            buf.Append("                {\r\n");
            foreach (IInstruction ins in rule.ImplicationInstructions)
            {
                buf.Append("                    ")
                    .Append(optimizer.MethodCallFor(ins))
                    .Append("; Thread.Sleep(delay);\r\n");
            }
            buf.Append("                }\r\n");
            buf.Append("                catch\r\n");
            buf.Append("                {\r\n");
            // consequences
            buf.Append("                    IList<String> consequences = new List<String>();\r\n");
            foreach(IConsequence consequence in rule.Consequences)
                buf.Append("                    consequences.Add(\"").Append(Utilities.Escape(consequence.Message)).Append("\");\r\n");
            buf.Append("                    return consequences;\r\n");
            buf.Append("                }\r\n");

            buf.Append("            }\r\n");
            buf.Append("            return new List<String>();\r\n");
            buf.Append("        }\r\n");
            return buf;
        }

        internal virtual StringBuilder RenderMethods(IEnumerable<IInstruction> instructions, ICompilerOptimizer optimizer)
        {
            StringBuilder buf = new StringBuilder();

            foreach (IInstruction ins in instructions)
            {
                buf.Append(optimizer.MethodBodyFor(ins));
            }
            return buf;
        }

        internal virtual String RenderRunMethod(IEnumerable<IInstruction> instructions, ICompilerOptimizer optimizer)
        {
            StringBuilder buf = new StringBuilder();
            int counter = 0;

            buf.Append("        public IEnumerable<IAutomationActionResult> Run(IBox box)\r\n");
            buf.Append("        {\r\n");
            buf.Append("            this.Box = box;\r\n");
            foreach (IInstruction ins in instructions)
            {
                buf.Append("            yield return ")
                    .Append(optimizer.MethodCallFor(ins))
                    .Append("; Thread.Sleep(delay);\r\n");
                counter++;
            }
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        

        internal virtual String RenderMainAndTestMethod(string scriptName, string controller)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append(RenderTestMethod(scriptName));
            buf.Append(RenderMainMethod(scriptName, controller));
            buf.Append("\r\n");
            return buf.ToString();
        }

        internal String RenderTestMethod(String scriptName)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append("        " + Asserts.TestAnnotation + "\r\n");
            buf.Append("        public void Test_").Append(scriptName).Append("()\r\n");
            buf.Append("        {\r\n");
            buf.Append("            Main();\r\n");
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        internal String RenderMainMethod(string scriptName, string controller)
        {
            StringBuilder buf = new StringBuilder();
            buf.Append("        [STAThread]\r\n");
            buf.Append("        public static void Main() \r\n");
            buf.Append("        {\r\n");
            buf.Append("            ").Append(scriptName).Append(" instance = new ").Append(scriptName).Append("();\r\n");
            buf.Append("            IController controller = new " + controller + "(instance);\r\n");
            buf.Append("            controller.Run();\r\n");
            buf.Append("        }\r\n");
            return buf.ToString();
        }

        internal virtual String RenderHeader()
        {
            return "using System;\r\n" +
Asserts.Header +
@"using System.Windows.Automation;
using System.Diagnostics;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Compiled_Tests;
using Compiled_Tests.Utils;
using Compiled_Tests.ProcessWrappers;
using RB = Compiled_Tests.RuleBase;
using LEET.CommonInterfaces.TestControls;

namespace Compiled_Tests
{
";
        }

        internal virtual String RenderScriptClassHeader(String scriptName, string interfase)
        {
            return "    "+Asserts.ClassAnnotation+"\r\n" + RenderRuleClassHeader(scriptName, interfase);
        }

        internal virtual String RenderRuleClassHeader(String scriptName, String interfase)
        {
            return @"    public class " + Helpers.NormalizeClassName(scriptName) + @" : " + interfase + @"
    {
        private IBox Box;
        private Utilities util = new Utilities();
        private int delay = Utilities.STD_DELAY;

";
        }

        internal virtual String RenderFooter()
        {
            return @"
    }
}
";
        }

        class CompilerOptimizer : ICompilerOptimizer
        {
            int i = 0;
            IDictionary<IInstruction, String> set = new Dictionary<IInstruction, String>();
            AssertionSet asserts;

            public CompilerOptimizer(AssertionSet asserts)
            {
                this.asserts = asserts;
            }

            public String MethodCallFor(IInstruction instruction)
            {
                return set[instruction];
            }

            public String MethodBodyFor(IInstruction instruction)
            {
                i++;
                if (!set.ContainsKey(instruction))
                {
                    set.Add(instruction, instruction.RenderMethodCall(i));
                    return instruction.RenderMethod(i, asserts).ToString();
                }
                else
                {
                    return "";
                }
            }

        }

        
    }
}
